﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SosaAnalysisModule.Data;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace SosaAnalysisModule.Modules
{
    public class ImageBuilder
    {
        /// <summary>
        /// takes group data and a directory and creates an image of the data from them
        /// </summary>
        /// <param name="groupData">contains all group attributes already except the list of sosadata</param>
        /// <param name="searchDirectory">directory used to fill in the list of sosadata</param>
        /// <output>an image at a specified directory</output>
        public static void CreateImage(Group[] groupData, string searchDirectory)
        {
            //fill in the SosaData lists for the groups
            ImageBuilder.CreateGroupData(searchDirectory, groupData);

            for (int i = 0; i < groupData.Length; i++)
                AnalysisModule.CreateGroupAnalysisFile(groupData[i]);
            
            //these are used to scale to the board
            double largestX = 0;
            double largestY = 0;

            //find the largest x and y
            double[] lPoints = LargestPoints(groupData);
            largestX = lPoints[0];
            largestY = lPoints[1];
            //making the bitmap
            //int sizeX = 750;
            //int sizeY = 750;
            //this has been taken out and hard coded as a set size, note below the 500 would have been (sizeX+sizeY)/3 
            double scalingFactor = (500) / (largestX + largestY);
            int sizeX = 800;
            int sizeY = 800 + groupData.Length * 20;
            //note the increase in size to keep points off the edge of the image

            //draw background
            Bitmap image = new Bitmap(sizeX, sizeY);
            var gr = Graphics.FromImage(image);
            SolidBrush whiteBrush = new SolidBrush(Color.White);
            Rectangle background = new Rectangle(0, 0, 800, 800);
            gr.FillRectangle(whiteBrush, background);

            //draw legend
            int legendX = 650;
            int legendY = 780;
            for (int i = 0; i < groupData.Length; i++)
            {
                gr.DrawString(groupData[i].Id, SystemFonts.DefaultFont, Brushes.Black, new RectangleF(legendX, legendY, 90, 50));
                SizeF size = gr.MeasureString(groupData[i].Id, SystemFonts.DefaultFont);
                SolidBrush brush = new SolidBrush(groupData[i].color);
                Rectangle rect = new Rectangle(legendX+Convert.ToInt32(size.Width), legendY, 16, 16);
                gr.FillEllipse(brush, rect);

                gr.DrawString(groupData[i].Name, SystemFonts.DefaultFont, Brushes.Black, new RectangleF(legendX + Convert.ToInt32(size.Width) + 16, legendY, 90, 50));
                legendY -= 20;
            }

            //draw groups
            for (int i = 0; i < groupData.Length; i++)
            {
                AddGroupToImage(groupData[i], image, scalingFactor);
            }

            //prompts the user for a directory to save the file to
            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
            dlg.FileName = "ImageComposition"; // Default file name
            dlg.DefaultExt = ".jpg"; // Default file extension
            dlg.Filter = "JPG Image (.jpg)|*.jpg"; // Filter files by extension 

            // Show save file dialog box
            Nullable<bool> result = dlg.ShowDialog();

            // Process save file dialog box results 
            if (result == true) 
                image.Save(dlg.FileName);

        }

        /// <summary>
        /// adds a group to an image
        /// </summary>
        /// <param name="groupData">a single group</param>
        /// <param name="image"></param>
        /// <param name="scalingFactor"></param>
        private static void AddGroupToImage(Group groupData, Bitmap image, double scalingFactor)
        {

            //error handling for empty groups
            if (groupData.data.Count == 0) return;

            for (int i = 0; i < groupData.data.Count; i++)
            {
                if (groupData.data[i] == null) return;
            }

            //number of pegs and average of positions
            int numPegs = groupData.data[0].PresentationOrder.Length;
            System.Windows.Point[] avgFinalPositions = Average(groupData);
            

            //transalating the points to match a bitmap
            for (int i = 0; i < numPegs; i++)
            {
                avgFinalPositions[i].X = avgFinalPositions[i].X * scalingFactor;
                avgFinalPositions[i].Y = avgFinalPositions[i].Y * scalingFactor;
                avgFinalPositions[i].X = avgFinalPositions[i].X + 700 / 2;
                avgFinalPositions[i].Y = Math.Abs(avgFinalPositions[i].Y - 700 / 2);
            }

            //drawing
            var gr = Graphics.FromImage(image);
            gr.SmoothingMode = SmoothingMode.AntiAlias;
            gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
            gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
            SolidBrush brush = new SolidBrush(groupData.color);
            for (int i = 0; i < numPegs; i++)
            {
                int x = Convert.ToInt32(avgFinalPositions[i].X - 8);
                int y = Convert.ToInt32(avgFinalPositions[i].Y - 8);
                Rectangle rect = new Rectangle(x, y, 16, 16);
                gr.FillEllipse(brush, rect);

                gr.DrawString(groupData.data[0].PresentationOrder[i].Label, SystemFonts.DefaultFont, Brushes.Black, new RectangleF(x, y - 20, 90, 50));
            }

            gr.Flush();

        }

        /// <summary>
        /// takes a directory and a list of groups and fills in the data of the groups
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="groups"></param>
        /// <returns>a list of groups</returns>
        private static Group[] CreateGroupData(String directory, Group[] groups)
        {

            SosaData[] sosaData = DirectoryReader.GetSosaData(directory);

            if (sosaData == null) return null;

            for (int i = 0; i < sosaData.Length; i++)
            {
                if (sosaData[i] == null){ return null;}
            }

            //find all unique experiment Id's
            for (int i = 0; i < groups.Length; i++)
            {
                //make a regex from group id
                string id = groups[i].Id;
                string regExpr = "";
                char[] idCharList = id.ToCharArray();
                for (int j = 0; j < idCharList.Length; j++)
                {
                    //if the wildcard character is present, add the shortcut for any character to the regex
                    if(idCharList[j].Equals('_')) regExpr = regExpr + "\\w";
                    else regExpr = regExpr + idCharList[j];
                }
                //match all sosadata whose Experiment Id match the regex
                for(int j = 0; j < sosaData.Length; j++)
                {
                    System.Text.RegularExpressions.Match match = System.Text.RegularExpressions.Regex.Match(sosaData[j].Subject, regExpr);
                    if(match.Success)
                    {
                        groups[i].data.Add(sosaData[j]);
                    }
                }
            }

            return groups;
        }

        /// <summary>
        /// finds the averages of pegs
        /// </summary>
        /// <param name="groupData"></param>
        /// <returns>a point array</returns>
        protected static System.Windows.Point[] Average(Group groupData)
        {
            int numPegs = groupData.data[0].PresentationOrder.Length;
            System.Windows.Point[] avgFinalPositions = new System.Windows.Point[numPegs];

            //getting averages
            for (int i = 0; i < groupData.data.Count; i++)
            {
                for (int j = 0; j < numPegs; j++)
                {
                    //get point in avg
                    double x = groupData.data[i].FinalPositions[j].position.X;
                    double y = groupData.data[i].FinalPositions[j].position.Y;
                    avgFinalPositions[j].X += x;
                    avgFinalPositions[j].Y += y;
                }
            }
            for (int i = 0; i < numPegs; i++)
            {
                avgFinalPositions[i].X = avgFinalPositions[i].X / groupData.data.Count;
                avgFinalPositions[i].Y = avgFinalPositions[i].Y / groupData.data.Count;
            }
            return avgFinalPositions;
        }

        /// <summary>
        /// finds the largest point
        /// returns an array to avoid doing 3 nested loops twice 
        /// </summary>
        /// <param name="groupData"></param>
        /// <returns>an array containing largest x and y</returns>
        protected static double[] LargestPoints(Group[] groupData)
        {
            double largestX = 0;
            double largestY = 0;
            for (int i = 0; i < groupData.Length; i++)
            {
                for (int j = 0; j < groupData[i].data.Count; j++)
                {
                    int numPegs = groupData[i].data[j].PresentationOrder.Length;

                    for (int k = 0; k < numPegs; k++)
                    {
                        double x = groupData[i].data[j].FinalPositions[k].position.X;
                        double y = groupData[i].data[j].FinalPositions[k].position.Y;

                        if (Math.Abs(x) > largestX) largestX = Math.Abs(x);
                        if (Math.Abs(y) > largestY) largestY = Math.Abs(y);
                    }
                }
            }
            double[] points = new double[2];
            points[0] = largestX;
            points[1] = largestY;
            return points;
        }
    }
}
